package server;

import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;

import net.Connection;
import net.ServerInfo;
import util.Logger;

/**
 * Manages available servers and creates local servers on the fly if they are
 * necessary.
 * 
 * @author Jason
 * 
 */
public abstract class DefaultGameHostProvider extends Server implements
		GameHostProvider {

	public static final int PORT = 21252;
	private static final Random rand = new Random();

	private final Queue<Connection> foreignServers = new LinkedList<Connection>();
	private final LinkedList<GameHostingServer> localServers = new LinkedList<GameHostingServer>();
	protected int maxLocalServers = 2;
	private final HashSet<Integer> portsUsed = new HashSet<Integer>();

	public DefaultGameHostProvider() throws IOException {
		super(PORT);
		portsUsed.add(MATCH_MAKING_PORT);
		portsUsed.add(PORT);
		startServer();
		System.out.println("DefaultGameHostProvider server started.");
	}

	/**
	 * @param max
	 *            The maximum number of game hosting servers to run on this
	 *            machine.
	 */
	public void setMaxLocalServers(int max) {
		this.maxLocalServers = max;
	}

	@Override
	public synchronized ServerInfo getNextServerAddress() {
		if (hasAvailableServer() == false)
			return null;
		Connection conn = foreignServers.poll();
		if (conn != null) {
			return new ServerInfo(conn.getIP(), conn.getTCPPort());
		} else {
			// start a new local server
			GameHostingServer server = startLocalServer();
			try {
				server.startServer();
				return new ServerInfo(IPLocater.getIP(), server.tcpPort);
			} catch (IOException e) {
				Logger.log(e);
				return null;
			}
		}
	}

	@Override
	public synchronized boolean hasAvailableServer() {
		return (!foreignServers.isEmpty() || localServers.size() < maxLocalServers);
	}

	@Override
	public synchronized void connectionReceived(Connection conn) {
		foreignServers.add(conn);
	}

	private GameHostingServer startLocalServer() {
		while (true) {
			try {
				GameHostingServer server = createHostingServer(IPLocater
						.getIP(), getPossiblePort(), getPossiblePort());
				localServers.add(server);
				return server;
			} catch (IOException e) {
				System.err.println("Error binding hosting server...retrying");
			}
		}
	}

	protected int getPossiblePort() {
		while (true) {
			int portNum = 20000 + rand.nextInt(10000);
			if (portsUsed.contains(portNum) == false) {
				portsUsed.add(portNum);
				return portNum;
			}
		}
	}

	public synchronized void dispose() {
		for (GameHostingServer g : localServers) {
			g.stopServer(true);
		}
		stopServer(true);
	}

	@Override
	public void receive(byte[] bytes, Connection conn) {
	}

	protected abstract GameHostingServer createHostingServer(
			String controllerIP, int tcpPort, int udpPort) throws IOException;

}
